/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * 知启蒙Servlet兼容库（zhiqim_servlet）在LGPL3.0协议下开源：https://www.zhiqim.com/gitcan/zhiqim/zhiqim_servlet.htm
 *
 * This file is part of [zhiqim_servlet].
 * 
 * [zhiqim_servlet] is free software: you can redistribute
 * it and/or modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * [zhiqim_servlet] is distributed in the hope that it will
 * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with [zhiqim_servlet].
 * If not, see <http://www.gnu.org/licenses/>.
 */
package org.zhiqim.servlet;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;

import javax.servlet.ServletContext;
import javax.servlet.SessionTrackingMode;

import org.zhiqim.httpd.HttpEntity;
import org.zhiqim.httpd.HttpException;
import org.zhiqim.httpd.HttpHandler;
import org.zhiqim.httpd.HttpHeader;
import org.zhiqim.httpd.HttpRequest;
import org.zhiqim.httpd.HttpResource;
import org.zhiqim.httpd.HttpResponse;
import org.zhiqim.httpd.HttpSender;
import org.zhiqim.httpd.HttpServer;
import org.zhiqim.httpd.HttpSessionManager;
import org.zhiqim.httpd.HttpWebsocket;
import org.zhiqim.httpd.context.ServiceExecutor;
import org.zhiqim.httpd.context.ZmlBootstrap;
import org.zhiqim.httpd.context.ZmlConfig;
import org.zhiqim.httpd.context.ZmlExecutor;
import org.zhiqim.httpd.context.config.ZAction;
import org.zhiqim.httpd.context.core.Action;
import org.zhiqim.httpd.context.core.Context;
import org.zhiqim.httpd.context.core.Interceptor;
import org.zhiqim.httpd.entities.NotFoundEntity;
import org.zhiqim.kernel.Global;
import org.zhiqim.kernel.config.Group;
import org.zhiqim.kernel.constants.ZhiqimConstants;
import org.zhiqim.kernel.extend.HashMapSO;
import org.zhiqim.kernel.logging.Log;
import org.zhiqim.kernel.logging.LogFactory;
import org.zhiqim.kernel.util.Arrays;
import org.zhiqim.kernel.util.DateTimes;
import org.zhiqim.kernel.util.Files;
import org.zhiqim.kernel.util.Ids;
import org.zhiqim.kernel.util.Ints;
import org.zhiqim.kernel.util.Longs;
import org.zhiqim.kernel.util.Strings;
import org.zhiqim.kernel.util.Validates;
import org.zhiqim.zml.ZmlEngine;
import org.zhiqim.zml.ZmlVariable;

/**
 * 实现ZML模式的上下文环境<br><br>
 * 1.实现自Context类，实现所有的上下文环境功能<br>
 * 2.加载/conf/context.xml为上下文的配置<br>
 *   2.1 Attribute为context属性配置<br>
 *   2.2 Interceptor为context的拦截器<br>
 *   2.3 Rule为在html中定义权限配置<br>
 *   2.4 Action为每path处理<br>
 * 3.加载/conf/context.zml为配置ZML定义<br>
 * 
 * @version v1.0.0 @author zouzhigang 2014-3-21 新建与整理
 */
public class ZmlContext implements Context, ZhiqimConstants
{
    private static final Log log = LogFactory.getLog(ZmlContext.class);
    private static final NotFoundEntity _notFound = new NotFoundEntity();
    
    private final String id;
    private final ServletContext context;
    private final ZmlExecutor zExecutor;
    private final ServiceExecutor sExecutor;
    private final HttpServer server;
    private final HttpResource resource;
    private final List<HttpResource> cResourceList = new ArrayList<HttpResource>();
    private final List<String> filters = new ArrayList<String>(2);
    
    private ZmlConfig config;                          //配置管理
    private ZmlSessionManager sessionManager;          //会话管理器
    private ZmlBootstrap bootstrap;                    //引导类
    private ZmlEngine engine;                      //默认引擎
    
    public ZmlContext(ServletContext context, ZmlExecutor executor, ServiceExecutor sExecutor)
    {
        this.id = Strings.trim(context.getServletContextName(), Ids.uuid());
        
        this.context = context;
        this.context.setAttribute(_HTTP_CONTEXT_CLASS_, this);
        
        this.zExecutor = executor;
        this.sExecutor = sExecutor;
        this.server = new HttpServer();
        this.server.setId(context.getServerInfo());
        this.server.addContext(this);
        this.server.setRunning();
        
        this.resource = new HttpResource(_DIRECTORY_, getResourcePath());
        
        this.addFilter(ZC_FILTER_ZTMPL_HTM);
        this.addFilter(ZC_FILTER_ZTMPL_ZML);
    }
    
    /************************************************************************/
    //初始化和销毁
    /************************************************************************/
    
    /** 初始化 */
    public boolean create(Group group)
    {
        if (!initConfig(group))
        {//初始化本类信息失败
            System.exit(1);
            return false;
        }
        
        if (!initZmlEngine())
        {// 初始化ZML引擎失败
            System.exit(1);
            return false;
        }
        
        if (!initContext())
        {//初始化上下文配置信息失败
            System.exit(1);
            return false;
        }
        
        if (!initBootstrap())
        {//初始化WEB引导类失败
            System.exit(1);
            return false;
        }
        
        return true;
    }
    
    /** 初始化配置信息 */
    public boolean initConfig(Group group)
    {
        //1.资源配置
        this.config = new ZmlConfig(this, resource, group.isTrue(_ZML_PATH_EXACTPRIOR_, true));
        
        //2.过滤配置
        String filterPattern = getTopAttributeString(_CONTEXT_FILTER_);
        if (Validates.isNotEmpty(filterPattern))
        {
            String[] filters = Arrays.toStringArray(filterPattern);
            for (String filter : filters)
            {
                addFilter(filter);
            }
        }
        
        //3.配置Session管理器
        this.sessionManager = new ZmlSessionManager(this); 
        this.context.addListener(sessionManager);
        
        //4.获取引类类配置，如果引导类配置不正确采用系统引导类
        this.bootstrap = config.getBootstrap();
        return true;
    }
    
    /**
     * 初始化ZML引擎
     * 
     * @throws Exception
     */
    private boolean initZmlEngine()
    {
        log.info("初始化[HTTP服务][ZML]开始...[%s]", DateTimes.getDateTimeSSSString());
        
        try
        {
            engine = new ZmlEngine();
            engine.setZmlVarNotice(config);
            engine.setMaxKeepTime(getAttributeInt(_ZML_MAX_KEEP_TIME_, 24*60*60));//24小时
            engine.setMaxIdleTime(getAttributeInt(_ZML_MAX_IDLE_TIME_, 1*60*60));//1小时
            engine.setFileZmlLoader(new File(getResourcePath()));
            
            log.info("初始化[HTTP服务][ZML]结束!!![%s]", DateTimes.getDateTimeSSSString());
            return true;
        }
        catch (IOException e)
        {
            log.error("初始化ZML引擎错误", e);
            return false;
        }
    }
    
    /** 初始化配置信息 */
    private boolean initContext()
    {
        log.info("初始化[HTTP服务:%s][Context:%s]开始...[%s]", getServerId(), getContextName(), DateTimes.getDateTimeSSSString());
        
        try
        {
            config.create();
            log.info("初始化[HTTP服务:%s][Context:%s]完成!!![%s]", getServerId(), getContextName(), DateTimes.getDateTimeSSSString());
            return true;
        }
        catch(Exception e)
        {
            log.error("初始化[HTTP服务:%s][Context:%s]失败!!!" , e, getServerId(), getContextName());
            return false;
        }
    }
    
    
    /** 初始化业务层引导类  */
    private boolean initBootstrap()
    {
        try
        {
            if (!bootstrap.isInitialized())
            {
                bootstrap.init(this);
                bootstrap.setInitialized(true);
            }
            
            return true;
        }
        catch(Exception e)
        {
            log.error("初始化业务引导类失败", e);
            return false;
        }
    }

    /** 释放资源 */
    public void destroy()
    {
        if (bootstrap != null){
            bootstrap.destroy();
        }
        
        if (config != null)
            config.destroy();
    }
    
    
    @Override
    public String getId()
    {
        return id;
    }
    
    @Override
    public boolean isRunning()
    {
        return true;
    }
    
    @Override
    public ZmlConfig getConfig()
    {
        return config;
    }
    
    /*********************************************************************/
    //上下文环境处理器
    /*********************************************************************/
    
    /**
     * 增加组件资源
     * 
     * @param resourceIndex 资源查找索引，从小到大查找
     * @param resourceType  资源类型classPath|directory
     * @param resourcePath  资源地址，如/org/zhiqim/或./portal
     * @return              =true表示成功，=false表示已存在
     */
    public boolean addComponentResource(int resourceIndex, String resourceType, String resourcePath)
    {
        HttpResource cResource = new HttpResource(resourceIndex, resourceType, resourcePath);
        if (cResourceList.contains(cResource))
            return false;
        
        cResourceList.add(cResource);
        Collections.sort(cResourceList, HttpResource._resourceCompor);
        return true;
    }
    
    /**
     * 获取该资源内的资源字符串
     * 
     * @param path          请求
     * @return              =null表示不在该资源内,!=null表示在该资源内并读取到内容
     * @throws IOException  异常
     */
    public String getResourceString(String path) throws IOException
    {
        return getResourceString(path, _UTF_8_);
    }
    
    /**
     * 获取该资源内的资源字符串
     * 
     * @param path          请求
     * @param encoding      编码
     * @return              =null表示不在该资源内,!=null表示在该资源内并读取到内容
     * @throws IOException  异常
     */
    public String getResourceString(String path, String encoding) throws IOException
    {
        String resourcePath = getRealPath(path);
        if (Files.isFile(resourcePath))
            return Files.read(resourcePath, encoding);
        
        //查找是否配置了组件资源目录列表
        for (HttpResource cResource : cResourceList)
        {
            String value = cResource.getResourceString(path, encoding);
            if (value != null)
                return value;
        }
        
        return null;
    }
    
    /*********************************************************************/
    //上下文环境处理器，和zhiqim_httpd不同，该处只是对组件资源的处理器，没有过滤和匹配
    /*********************************************************************/
    
    /**
     * 查找对应的Handler并处理，和zhiqim_httpd不同，只处理来自request.forwardTo的处理
     * 
     * @param request           请求
     * @param response          响应
     * @throws HttpException    HTTP异常
     * @throws IOException      IO异常
     */
    public void handle(HttpRequest request, HttpResponse response) throws HttpException, IOException
    {
        zExecutor.handle(request, response);
    }
    
    /*********************************************************************/
    //上下文环境基本属性相关方法
    /*********************************************************************/

    /** 获取上下文环境路径 */
    public String getContextPath()
    {
        return context.getContextPath();
    }
    
    /** 获取上下文环境资源路径 */
    public String getResourcePath()
    {
        return context.getRealPath("");
    }
    
    /** 获取上下文环境下绝对路径转为根环境下的绝对路径，如contextPath=/doc,path=/index.htm，得到/doc/index.htm */
    public String getRootPath(String path)
    {
        path = Strings.trim(path);
        if (!Strings.startsWith(path, "/"))
            return path;//相对路径不处理
        
        return Strings.trimRight(context.getContextPath(), "/") + path;
    }
    
    /** 获取上下文环境对应的真实路径 */
    public String getRealPath(String path)
    {
        path = Files.toLinuxPath(Strings.trim(path));
        if (Validates.isEmpty(path) || "/".equals(path))
            return getResourcePath();
        
        return context.getRealPath(path);
    }
    
    /** 获取上下文环境欢迎页 */
    public String getWelcomeUrl()
    {
        Object value = context.getAttribute(_CONTEXT_WELCOME_URL_);
        return (value == null)?null:(String)value;
    }
    
    /** 获取上下文环境404页跳转页 */
    public String getNotFoundUrl()
    {
        Object value = context.getAttribute(_CONTEXT_NOT_FOUND_URL_);
        return (value == null)?null:(String)value;
    }
    
    /** 获取上下文环境404页处理器 */
    public HttpEntity getNotFoundHandler()
    {
        return _notFound;
    }
    
    public boolean isUseCookie()
    {
        Set<SessionTrackingMode> modes = context.getDefaultSessionTrackingModes();
        return modes.contains(SessionTrackingMode.COOKIE);
    }
    
    @Override
    public boolean isClasspath()
    {
        return false;
    }
    
    /** 获取上下文环境会话管理器 */
    public HttpSessionManager getSessionManager()
    {
        return sessionManager;
    }
    
    @Override
    public void invalidateSession(String sessionId)
    {
        sessionManager.invalidateSession(sessionId);
    }
    
    /*********************************************************************/
    //上下文环境自定义属性相关方法
    /*********************************************************************/
    
    /** 设置属性 */
    public void setAttribute(String key, Object value)
    {
        context.setAttribute(key, value);
    }
    
    @Override
    public boolean hasAttribute(String key)
    {
        return context.getAttribute(key) != null;
    }
    
    /** 判断是否有顶级属性 */
    public boolean hasTopAttribute(String key)
    {
        return context.getAttribute(key) != null;
    }
    
    /** 获取顶有属性对象 */
    public Object getTopAttribute(String key)
    {
        return context.getAttribute(key);
    }
    
    /** 获取字符串 ，如果属性不存在则使用缺省值*/
    public String getTopAttributeString(String key)
    {
        Object value = getTopAttribute(key);
        return (value == null)?null:Strings.trim(String.valueOf(value));
    }
    
    /** 获取属性表 */
    public HashMapSO getAttributes()
    {
        HashMapSO attributeMap = new HashMapSO();
        Enumeration<String> enums = context.getAttributeNames();
        while (enums.hasMoreElements())
        {
            String key = enums.nextElement();
            attributeMap.put(key, context.getAttribute(key));
        }
        return attributeMap;
    }
    
    /** 获取属性 */
    public Object getAttribute(String key)
    {
        return ZmlVariable.chkDynamicVar(config.get(key));
    }
    
    /** 获取属性，如果属性不存在则使用缺省值 */
    public Object getAttribute(String key, Object defaultValue)
    {
        Object value = getAttribute(key);
        return (value == null)?defaultValue:value;
    }
    
    /** 获取上下文环境名称 */
    public String getContextName()
    {
        String value = getAttributeString(_NAME_);
        return value == null?Global.getName():value;
    }
    
    /** 获取上下文环境版本 */
    public String getContextVersion()
    {
        String value = getAttributeString(_VERSION_);
        return value == null?Global.getVersion():value;
    }
    
    /** 获取字符串 */
    public String getAttributeString(String key)
    {
        return getAttributeString(key, null);
    }
    
    /** 获取字符串 ，如果属性不存在则使用缺省值*/
    public String getAttributeString(String key, String defaultValue)
    {
        Object value = getAttribute(key);
        return (value == null)?defaultValue:Strings.trim(String.valueOf(value));
    }
    
    /** 获取整型 */
    public int getAttributeInt(String key)
    {
        return getAttributeInt(key, -1);
    }
    
    /** 获取整型 ，如果属性不存在则使用缺省值 */
    public int getAttributeInt(String key, int defaultValue)
    {
        return Ints.toInt(getAttribute(key), defaultValue);
    }
    
    /** 获取长整型 */
    public long getAttributeLong(String key)
    {
        return getAttributeLong(key, -1);
    }
    
    /** 获取长整型 ，如果属性不存在则使用缺省值 */
    public long getAttributeLong(String key, long defaultValue)
    {
        return Longs.toLong(getAttribute(key), defaultValue);
    }
    
    /** 获取布尔型 ，如果属性不存在则使用缺省值 */
    public boolean getAttributeBoolean(String key, boolean defaultValue)
    {
        Object value = getAttribute(key);
        if (value == null)
            return defaultValue;
        else if (value instanceof Boolean)
            return (Boolean)value;
        else if (value instanceof String)
            return Boolean.parseBoolean((String)value);
        else
            return defaultValue;
    }
    
    /************************************************************************/
    //Bootstrap & RMIServer & Zml
    /************************************************************************/
    
    @Override
    public void setServer(HttpServer server)
    {
    }

    @Override
    public HttpServer getServer()
    {
        return server;
    }
    
    @Override
    public String getServerId()
    {
        return server.getId();
    }
    
    @Override
    public ZmlBootstrap getBootstrap()
    {
        return bootstrap;
    }
    
    @Override
    public ServiceExecutor getServiceExecutor()
    {
        return sExecutor;
    }
    
    @Override
    public ZmlEngine getZmlEngine()
    {
        return engine;
    }
    
    /************************************************************************/
    //Action & Interceptor
    /************************************************************************/
    
    @Override
    public Action getAction(ZAction fAction)
    {
        return config.getAction(fAction);
    }
    
    @Override
    public boolean hasActionInstance(String clazz)
    {
        return getActionInstance(clazz) != null;
    }
    
    @Override
    public Action getActionInstance(String clazz)
    {
        return config.getActionInstance(clazz);
    }
    
    @Override
    public ZAction getAction(String path)
    {
        return config.getAction(path);
    }
    
    @Override
    public boolean hasInterceptor(String interceptor)
    {
        return config.hasInterceptor(interceptor);
    }
    
    @Override
    public Interceptor getInterceptor(String interceptor)
    {
        return config.getInterceptor(interceptor);
    }
    
    @Override
    public List<Interceptor> getInterceptorList(String interceptors)
    {
        if (Validates.isEmpty(interceptors))
            return new ArrayList<Interceptor>(0);
        
        List<Interceptor> interceptorList = new ArrayList<Interceptor>();
        
        String[] interceptorArr = Arrays.toStringArray(interceptors);
        for (String interceptor : interceptorArr)
        {
            Interceptor ic = getInterceptor(interceptor);
            if (ic != null)
                interceptorList.add(ic);
        }
        
        return interceptorList;
    }

    @Override
    public void handleResource(HttpHeader header, HttpSender sender) throws HttpException, IOException
    {
        //查找是否配置了资源目录
        if (resource != null)
        {
            if (resource.handleResource(header, sender))
                return;
        }

        //查找是否配置了组件资源目录列表
        for (HttpResource cResource : cResourceList)
        {
            if (cResource.handleResource(header, sender))
                return;
        }
        
        //最后未找到处理
        _notFound.handle(header, sender);
    }
    
    /**
     * 处理组件中配置的资源
     * 
     * @param request           请求
     * @param response          响应
     * @throws HttpException    HTTP异常
     * @throws IOException      IO异常
     */
    void handleComponentResource(HttpRequest request, HttpResponse response) throws HttpException, IOException
    {
        //查找是否配置了组件资源目录列表
        for (HttpResource cResource : cResourceList)
        {
            if (cResource.handleResource(request, response))
                return;
        }
        
        _notFound.handle(request, response);
    }
    
    @Override
    public List<String> getResourceNameList(String folderPath) throws IOException
    {
        List<String> nameList = new ArrayList<>();
        if (resource != null)
        {
            nameList.addAll(resource.getResourceNameList(folderPath));
        }

        //查找是否配置了组件资源目录列表
        for (HttpResource cResource : cResourceList)
        {
            nameList.addAll(cResource.getResourceNameList(folderPath));
        }
        
        return nameList;
    }
    
    /*********************************************************************/
    //上下文环境过滤和匹配相关
    /*********************************************************************/
    
    /**
     * 增加过滤路径,规则同Handler匹配规则,不允许增加缺省/的过滤
     * 
     * @param match 匹配字符串
     * @return      =true表示增加成功，=false表示增加失败
     */
    public boolean addFilter(String match)
    {
        if ("/".equals(match) || !isValidMatch(match))
            return false;
            
        filters.add(match);
        return true;
    }
    
    /** 是否是有效的匹配 */
    private boolean isValidMatch(String match)
    {
        if (match == null)
            return false;
        
        if ("/".equals(match))
            return true;//缺省匹配
        
        int times = Strings.getTimes(match, '*');
        if (times == 0 && match.startsWith("/"))
            return true;//精确匹配,如/index.htm
        
        if (times == 1 && match.startsWith("/") && match.endsWith("/*"))
            return true;//路径匹配,如/service/*
        
        if (times == 1 && (match.indexOf("/*.") != -1 || match.startsWith("*.")) && !match.endsWith("."))
            return true;//扩展匹配,如*.htm,或指定路径的扩展匹配/service/*.htm
        
        return false;
    }
    
    @Override
    public boolean isFilterPath(String pathInContext)
    {
        for (String filter : filters)
        {
            if (filter.equals(pathInContext))
                return true;//精确
            
            if (filter.endsWith("/*"))
            {
                filter = filter.substring(0, filter.length()-1);
                if (pathInContext.startsWith(filter))
                    return true;//路径
            }
            
            if (filter.startsWith("*."))
            {
                filter = filter.substring(1);
                if (pathInContext.endsWith(filter))
                    return true;//扩展
            }
            
            int ind = filter.indexOf("/*.");
            if (ind != -1)
            {//指定路径的扩展,如/service/*.js
                String pathPrefix = filter.substring(0, ind+1);// /service/
                String pathSuffix = filter.substring(ind+2);// .js
                if (pathInContext.startsWith(pathPrefix) && pathInContext.endsWith(pathSuffix))
                    return true;
            }
        }
        
        return false;
    }

    @Override
    public HttpHandler getMatchHandler(String pathInContext)
    {//所有匹配到ZML处理器
        return zExecutor;
    }

    @Override
    public String getCookieDomain()
    {
        return null;
    }

    @Override
    public String getCookiePath()
    {
        return null;
    }

    @Override
    public int getCookiePort()
    {
        return 0;
    }

    @Override
    public HttpWebsocket getWebsocket(String protocol)
    {//不支持
        return null;
    }
    
    @Override
    public int getChunkSize()
    {//不支持
        return 0;
    }

    @Override
    public int getMaxContentLength()
    {//不支持
        return 0;
    }

    @Override
    public int getExpires()
    {
        return 0;
    }

    @Override
    public boolean isCookieUse()
    {
        return true;
    }
}
